/*
 * Copyright (c) 2022, kaans, <https://github.com/kaans>
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include "esp_log.h"
#include <esp_partition.h>
#include <spi_flash_mmap.h>

#include <sys/time.h>
#include <string.h>
#include <fal.h>
#include <flashdb.h>

#include "fal_cfg.h"

#define FLASH_ERASE_MIN_SIZE SPI_FLASH_SEC_SIZE

#define LOCKER_ENABLE
#ifdef LOCKER_ENABLE
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"

static SemaphoreHandle_t s_lock = NULL;
static SemaphoreHandle_t s_lock_s = NULL;
static uint32_t event_ids = 0xFFFFFFFF;
static uint32_t log_ids = 0xFFFFFFFF;
static struct fdb_tsdb event_tsdb = {0}; /* Event TSDB object */
static struct fdb_tsdb log_tsdb = {0};   /* Log TSDB object */

const static esp_partition_t *partition;

#define LOCK() xSemaphoreTake(s_lock, portMAX_DELAY)

#define UNLOCK() xSemaphoreGive(s_lock)
#else
#define LOCK()
#define UNLOCK()
#endif

static int init(void)
{
    // the values passed to esp_partition_find_first() must correspond to the
    // values set in partitions.csv for the partition named "flashdb".
    partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_UNDEFINED, "log");

    assert(partition != NULL);
    assert(partition->size >= CONFIG_FLASHDB_FLASH_SIZE);

    return 1;
}

static int read(long offset, uint8_t *buf, size_t size)
{
    esp_err_t ret;

    LOCK();
    ret = esp_partition_read(partition, offset, buf, size);
    UNLOCK();

    return ret;
}

static int write(long offset, const uint8_t *buf, size_t size)
{
    esp_err_t ret;

    LOCK();
    ret = esp_partition_write(partition, offset, buf, size);
    UNLOCK();

    return ret;
}

static int erase(long offset, size_t size)
{
    esp_err_t ret;
    int32_t erase_size = ((size - 1) / FLASH_ERASE_MIN_SIZE) + 1;

    LOCK();
    ret = esp_partition_erase_range(partition, offset, erase_size * FLASH_ERASE_MIN_SIZE);
    UNLOCK();

    return ret;
}

const struct fal_flash_dev nor_flash0 = {
    .name = NOR_FLASH_DEV_NAME,
    .addr = 0x0,                      // address is relative to beginning of partition; 0x0 is start of the partition
    .len = CONFIG_FLASHDB_FLASH_SIZE, // size of the partition as specified in partitions.csv
    .blk_size = FLASH_ERASE_MIN_SIZE, // must be 4096 bytes
    .ops = {init, read, write, erase},
    .write_gran = 1, // 1 byte write granularity
};

static void lock(fdb_db_t db)
{
    xSemaphoreTake(s_lock_s, portMAX_DELAY);
}
static void unlock(fdb_db_t db)
{
    xSemaphoreGive(s_lock_s);
}

static fdb_time_t event_get_time(void)
{
    return ++event_ids; /* 按顺序写入 */
}
static fdb_time_t log_get_time(void)
{
    return ++log_ids; /* 按顺序写入 */
}

void fdb_init(void)
{
#ifdef LOCKER_ENABLE
    if (s_lock == NULL)
    {
        s_lock = xSemaphoreCreateCounting(1, 1);
        assert(s_lock != NULL);
    }
#endif
    if (s_lock_s == NULL)
    {
        s_lock_s = xSemaphoreCreateCounting(1, 1);
        assert(s_lock_s != NULL);
    }
    fdb_tsdb_control(&event_tsdb, FDB_TSDB_CTRL_SET_LOCK, lock);
    fdb_tsdb_control(&event_tsdb, FDB_TSDB_CTRL_SET_UNLOCK, unlock);
    fdb_tsdb_control(&log_tsdb, FDB_TSDB_CTRL_SET_LOCK, lock);
    fdb_tsdb_control(&log_tsdb, FDB_TSDB_CTRL_SET_UNLOCK, unlock);
    fdb_err_t result = fdb_tsdb_init(&event_tsdb, "port", "event", event_get_time, 5, NULL);
    assert(result == FDB_NO_ERR);
    result = fdb_tsdb_init(&log_tsdb, "port", "log", log_get_time, 30, NULL);
    assert(result == FDB_NO_ERR);
    fdb_tsdb_control(&event_tsdb, FDB_TSDB_CTRL_GET_LAST_TIME, &event_ids);
    fdb_tsdb_control(&log_tsdb, FDB_TSDB_CTRL_GET_LAST_TIME, &log_ids);
    // fdb_tsl_clean(&event_tsdb);
    // fdb_tsl_clean(&event_tsdb);
}

void event_push(uint8_t *data, uint8_t len)
{
    if (event_ids == 0xFFFFFFFF)
        return;
    struct fdb_blob blob = {0};
    fdb_err_t err = fdb_tsl_append(&event_tsdb, fdb_blob_make(&blob, data, len));
    if (err != FDB_NO_ERR)
    {
        printf("Failed to append event log(%lu:%X), err:%d\r\n", *(uint32_t *)data, data[4] & 0x1F, err);
    }
    // else
    // {
    //     printf("[%lu] append event log(%lu:%X)\r\n", event_ids - 1, *(uint32_t *)data, data[4] & 0x1F);
    // }
}

void event_get_all(fdb_tsl_cb cb)
{
    fdb_tsl_iter(&event_tsdb, cb, &event_tsdb);
}

void event_clear(void)
{
    fdb_tsl_clean(&event_tsdb);
}

void log_push(uint8_t *data, uint8_t len)
{
    if (log_ids == 0xFFFFFFFF)
        return;
    struct fdb_blob blob = {0};
    fdb_err_t err = fdb_tsl_append(&log_tsdb, fdb_blob_make(&blob, data, len));
    if (err != FDB_NO_ERR)
    {
        printf("Failed to append log(%lu), err:%d\r\n", *(uint32_t *)data, err);
    }
    // else
    // {
    //     printf("[%lu] append log(%lu)\r\n", log_ids - 1, *(uint32_t *)data);
    // }
}
void log_get_all(fdb_tsl_cb cb)
{
    fdb_tsl_iter(&log_tsdb, cb, &log_tsdb);
}

void log_clear(void)
{
    fdb_tsl_clean(&log_tsdb);
}
